import React, {
  memo,
  useState,
  useMemo,
  useEffect,
  useCallback,
  useRef,
  forwardRef,
  useImperativeHandle,
} from "react";
import { Table as AntdTable, Tooltip, Skeleton, Pagination } from "antd";
import { PaginationProps } from "antd/lib/pagination";
import classnames from "classnames";
import axios, { CancelTokenSource } from "axios";
import {
  TableProps,
  ColumnProps,
  SizeProps,
  TableRefProps,
  SelectedRowKeys,
} from "./interface";
import {
  ROW_HEIGHT,
  formatRenderText,
  typeToText,
  handleSelectedRows,
  getSelectedRows,
  flatSelectedRowKeys,
  removeSelectedRowKeys,
  getVisibleHeight,
  handSorter,
  CURR_PAGE_SIZE,
  CURR_COLUMN_WIDTH,
  setColumnsWidth,
  _setSorterKey,
  _getSorterKey
} from "./util";
import styles from "./table.module.scss";
import ResizeableTitle from "./ResizeableTitle";
import ToolBar from "./ToolBar";
import Checkbox from "./Checkbox";
import Empty from "./Empty";
import { getList } from "./api";

const ROW_KEY = "id";
const PAGE_SIZE = 20;
const LIMIT_MAX_SIZE = 15;
const pageSizeOptions = [
  "20",
  "50",
  "100",
  "200",
  "500",
  "800",
  "1000",
  "2000",
];
const emptyObject = {};
const emptyArray: any[] = [];
const CancelToken = axios.CancelToken;

const paginationConfig: PaginationProps = {
  showSizeChanger: true,
  showTotal: (total: number) => `${total} total`,
};

function Table<RecordType extends object = any>(
  {
    columns,
    url,
    extra,
    query,
    rowSelectionType,
    autoLoad = true,
    toolNode,
    flat,
    rowKey,
    limitMaxHeight,
    onPageChange,
    onDoubleClick,
    beforeUrlApi,
    handleResult,
    rowSelectionChange,
    rowSelectionDisabled,
    scroll: propScroll,
    dataSource: appointDataSource,
    recordSort,
    setTabsCount
  }: TableProps<RecordType>,
  ref: React.Ref<TableRefProps>
) {
  const serverTimer = useRef<number | null>(null);
  const rowClickTimer = useRef<number | null>(null);
  const boxRef = useRef<HTMLDivElement>(null);
  const beforeQuery = useRef(query);
  const beforePage = useRef(1);
  const beforePageSize = useRef(PAGE_SIZE);
  const selectedRows = useRef<RecordType[]>(emptyArray);
  const currRowClick = useRef(""); // 记录单击
  const prevCancelTokenSource = useRef<CancelTokenSource | null>(null);
  const columnsRef = useRef<any>(columns);

  const [dataSource, setDataSource] = useState<RecordType[]>(emptyArray);
  const [count, setCount] = useState(0);
  const [sorterKey, setSorterKey] = useState({});
  const [scroll, setScroll] = useState(emptyObject);
  const [page, setPage] = useState(1);
  const [pageSize, setPageSize] = useState(PAGE_SIZE);
  const [loading, setLoading] = useState(false);
  const [firstLoading, setFirstLoading] = useState(true);
  const [dragColumnWidthMap, setDragColumnWidthMap] = useState(emptyObject); // 拖动时 记录column 宽度
  const [selectedRowKeys, setSelectedRowKeys] =
    useState<SelectedRowKeys>(emptyObject);
  const [isPagination, setIsPagination] = useState(true);

  const _dataSource = appointDataSource || dataSource;
  const _rowKey = (rowKey || ROW_KEY) as string;

  const getListServer = useCallback(
    async (page: number, pageSize: number, query?: object) => {
      if (!url) return;

      if (pageSize) window.sessionStorage.setItem(CURR_PAGE_SIZE, pageSize.toString());

      await beforeUrlApi?.();

      if (serverTimer.current) {
        clearTimeout(serverTimer.current);
        serverTimer.current = null;
      }

      prevCancelTokenSource.current = CancelToken.source();

      serverTimer.current = window.setTimeout(async () => {
        setLoading(true);

        let result = await getList(
          {
            url,
            page,
            pageSize,
            query,
          },
          prevCancelTokenSource.current!
        );

        if (handleResult) result = handleResult?.(result);

        setDataSource(result.rows);
        setCount(result.count);
        setLoading(false);
        setFirstLoading(false);

        serverTimer.current = null;
      }, 200);
    },
    [url]
  );

  useImperativeHandle(ref, () => ({
    getDataSource: () => _dataSource,
    getQuery: () => ({ pages: { pageSize, page }, ...query }),
    getSelectedRows: () =>
      getSelectedRows(selectedRowKeys, selectedRows.current, _rowKey),

    setSelectRowKeys: (keys: string[], rows?: RecordType[]) => {
      setSelectedRowKeys({
        ...selectedRowKeys,
        [page]: (selectedRowKeys[page] || []).concat(keys),
      });
      if (rows) selectedRows.current = selectedRows.current.concat(rows);
    },
    getSelectRow: () => {
      return { selectedRowKeys, selectedRows: selectedRows.current };
    },
    setDefaultRow: (defSelectedRowKeys: SelectedRowKeys, rows?: RecordType[]) => {
      setSelectedRowKeys({ ...selectedRowKeys, ...defSelectedRowKeys });
      if (rows?.length) selectedRows.current = rows;
    },
    removeSelectedRowKeys: (rowKeys: string[]) => {
      return setSelectedRowKeys((prev) => removeSelectedRowKeys(rowKeys, prev));
    },

    onRefresh: () => {
      setSelectedRowKeys({});
      selectedRows.current = [];

      if (page !== 1) return setPage(1);
      getListServer(page, pageSize, query);
    },

    resetScroll: () => {
      if (boxRef.current) {
        const { offsetHeight } = boxRef.current;

        setScroll({
          x: scroll["x"],
          y: offsetHeight - ROW_HEIGHT,
        });
      }
    },
  }));

  useEffect(() => {
    setTabsCount?.(count!);
  }, [count]);

  useEffect(
    () => appointDataSource && setFirstLoading(false),
    [appointDataSource]
  );

  // 处理是否展示分页
  useEffect(() => {
    // 外部指定数据 不显示分页
    if (appointDataSource) return setIsPagination(false);

    // 数据长度大于pageSize 接口不支持分页
    if (dataSource.length > pageSize || !count) return setIsPagination(false);
    else setIsPagination(true);
  }, [appointDataSource, pageSize, dataSource, count]);

  useEffect(() => {
    const pages = query?.pages;

    if (pages) {
      setPage(pages?.page);
      setPageSize(pages?.pageSize);
      delete query.pages;
    }
  }, [query]);

  useEffect(() => {
    if (!boxRef.current) return;

    const { offsetHeight, offsetWidth } = boxRef.current as HTMLDivElement;
    const columnsTotalWidth = columns.reduce(
      (next, cur) => {
        next += dragColumnWidthMap[cur.dataIndex as string] || Number(cur.width) || 0;
        return next;
      },
      0
    );
    const len = _dataSource.length;

    let scrollY: any = undefined;

    if (limitMaxHeight) {
      if (len > LIMIT_MAX_SIZE) scrollY = ROW_HEIGHT * LIMIT_MAX_SIZE;
    } else {
      scrollY = getVisibleHeight(offsetHeight, len);
    }

    setScroll({
      x: columnsTotalWidth > offsetWidth ? "max-content" : undefined,
      y: scrollY,
    });
  }, [isPagination, toolNode, _dataSource, selectedRowKeys, columns, dragColumnWidthMap]);

  useEffect(() => {
    if (beforeQuery.current !== query) {

      if (!beforeQuery.current && !autoLoad) {
        beforeQuery.current = query;
        return;
      }
      // query change
      prevCancelTokenSource.current?.cancel();

      setSelectedRowKeys({});
      selectedRows.current = [];
      setDataSource([]);

      beforeQuery.current = query;
      beforePage.current = 0;

      if (page !== 1) return setPage(1);
    }

    if (!autoLoad) {
      if (beforePage.current !== page || beforePageSize.current !== pageSize) {
        getListServer(page, pageSize, query);

        beforePage.current = page;
        beforePageSize.current = pageSize;
      }
    } else {
      getListServer(page, pageSize, query);
    }
  }, [url, page, pageSize, query, getListServer]);

  useEffect(() => {
    if (!dataSource.length || !onPageChange) return;
    if (page === (dataSource[0] as any)._page) onPageChange(dataSource, page);
  }, [dataSource, page, onPageChange]);

  useEffect(() => {
    return () => {
      window.sessionStorage.removeItem(CURR_COLUMN_WIDTH);
      window.sessionStorage.removeItem(CURR_PAGE_SIZE);
    };
  }, []);

  useEffect(() => {
    if (columns.length > 1) {
      const column: any = columns?.[0] || columns?.[1]; // 防止手动加入的action没有pageSize
      const columnOrigin: any = columnsRef.current?.[0] || columnsRef.current?.[1];
      const pageSize = column?.pageSize && Number(column?.pageSize);
      const originPageSize = columnOrigin?.pageSize && Number(columnOrigin?.pageSize);

      if (pageSize !== originPageSize) {
        setPageSize((prev) => +column.pageSize || prev || PAGE_SIZE);
        columnsRef.current = columns;
      }
    }
  }, [columns]);

  const render = useCallback(
    (text: string, record: RecordType, index, column) => {
      let handleValue: any = typeToText(text);
      let render =
        column.render ||
        extra?.find((item) => item.dataIndex === column.dataIndex)?.render;

      if (render) handleValue = render(text, record, index);

      if (column.format)
        handleValue = formatRenderText(handleValue, column.format);

      if (Object.prototype.toString.call(handleValue) === '[object Object]' && !React.isValidElement(handleValue)) {
        handleValue = '';
      }

      if (Array.isArray(handleValue) && !React.isValidElement(handleValue[0])) {
        handleValue = '';
      }

      return column.tooltip ? (
        <Tooltip title={handleValue}>
          <span>{handleValue}</span>
        </Tooltip>
      ) : (
        handleValue
      );
    },
    [extra]
  );

  const onResize = useCallback(
    (column: ColumnProps<RecordType>) => {
      return (e: React.BaseSyntheticEvent, { size }: { size: SizeProps; }) => {
        if (!column.dataIndex) return;

        const widths = {
          ...dragColumnWidthMap,
          [column.dataIndex as string]: size.width,
        };
        setDragColumnWidthMap(widths);
      };
    },
    [dragColumnWidthMap]
  );

  const _columns = useMemo(() => {
    return columns.map((item: ColumnProps<RecordType> & { uuid?: string; }) => {
      const dataIndex = item.dataIndex as string;
      const title = item.title || window.$app.t(dataIndex);
      const width = dragColumnWidthMap[dataIndex] || item.width;
      const align = item.format === "money" ? "right" : item.align;

      if (item.uuid) setColumnsWidth(item.uuid, width);

      const sorter =
        item.sorter === false ||
        item.dataIndex === "action" ||
        extra?.find((extraItem) => extraItem.dataIndex === item.dataIndex)
          ?.headerRender;

      const newColumn = {
        ...item,
        title,
        width,
        sorter: sorter ? null : handSorter(item),
        align,
        className: item.dataIndex,
        render: (tx: string, record: RecordType, i: number) =>
          render(tx, record, i, item),
        onHeaderCell: (column: ColumnProps<RecordType>) => ({
          width: column.width,
          backgroundColor: column.backgroundColor,
          color: column.color,
          fontSize: column.fontSize,
          fontWeight: column.fontWeight,
          customizeStyleFlag: column.customizeStyleFlag,
          onResize: onResize(column),
        }),
        sortOrder: recordSort ? _getSorterKey(dataIndex, recordSort) : undefined
      };
      return newColumn;
    });
  }, [columns, render, onResize, dragColumnWidthMap, extra, recordSort, sorterKey]);

  const selectionChange = (checked: boolean, key: string) => {
    let curPageCheckedKeys: string[] = [];
    let newSelectedRowKeys = {};

    if (rowSelectionType === "radio") {
      curPageCheckedKeys = [key];
      newSelectedRowKeys = { [page]: [key] };
    } else {
      curPageCheckedKeys = selectedRowKeys[page] || [];

      if (checked) curPageCheckedKeys = curPageCheckedKeys.concat([key]);
      else
        curPageCheckedKeys = curPageCheckedKeys.filter((item) => item !== key);

      newSelectedRowKeys = { ...selectedRowKeys, [page]: curPageCheckedKeys };
    }

    setSelectedRowKeys(newSelectedRowKeys);

    const rows = _dataSource.filter((item) =>
      curPageCheckedKeys.includes(item[_rowKey])
    );

    selectedRows.current = handleSelectedRows(rows, selectedRows.current, _rowKey);
    rowSelectionChange?.(
      flatSelectedRowKeys(newSelectedRowKeys),
      selectedRows.current,
      { [key]: checked }
    );
  };

  const onSelectAll = () => {
    let curPageCheckedKeys = selectedRowKeys[page] || [];
    const effective = _dataSource.filter((item) =>
      rowSelectionDisabled ? !rowSelectionDisabled(item) : true
    );

    if (curPageCheckedKeys.length === effective.length) curPageCheckedKeys = [];
    else
      curPageCheckedKeys = curPageCheckedKeys = effective.map(
        (item) => item[_rowKey]
      );

    const newSelectedRowKeys = {
      ...selectedRowKeys,
      [page]: curPageCheckedKeys,
    };
    setSelectedRowKeys(newSelectedRowKeys);

    const rows = _dataSource.filter((item) =>
      curPageCheckedKeys.includes(item[_rowKey])
    );
    selectedRows.current = handleSelectedRows(rows, selectedRows.current, _rowKey);
    rowSelectionChange?.(
      flatSelectedRowKeys(newSelectedRowKeys),
      selectedRows.current
    );
  };

  const renderCell = (checked: boolean, record: any, index: number) => {
    return (
      <Checkbox
        disabled={rowSelectionDisabled?.(record, index)}
        onChange={selectionChange}
        rowKey={record[_rowKey]}
        type={rowSelectionType as string}
        checked={checked}
      />
    );
  };

  const currentPageChecked = useMemo(() => {
    const currentPageRowKeys = selectedRowKeys[page] || [];
    const effective = _dataSource.filter((item) =>
      rowSelectionDisabled ? !rowSelectionDisabled(item) : true
    );

    return currentPageRowKeys.length === effective.length;
  }, [selectedRowKeys, page, _dataSource]);

  const paginationChange = (page: number, _pageSize: number | undefined) => {
    setPage(_pageSize !== pageSize ? 1 : page);

    if (_pageSize) {
      setPageSize(_pageSize);

      if (_pageSize < pageSize) setDataSource([]);
    }
  };

  const clearCurrRowClick = () => {
    currRowClick.current = "";

    if (rowClickTimer.current) {
      window.clearTimeout(rowClickTimer.current);
      rowClickTimer.current = null;
    }
  };

  const onRow = (record: RecordType) => {
    return {
      onClick: (e: React.BaseSyntheticEvent) => {
        if (
          e.target.classList.contains("ant-table-cell") ||
          e.target.classList.contains("ant-tooltip-open") ||
          e.target.dataset.click
        ) {
          const onClick = () => {
            if (rowSelectionDisabled?.(record)) return;

            if (rowSelectionType === "radio") {
              selectionChange(true, record[_rowKey]);
            } else {
              const bool = flatSelectedRowKeys(selectedRowKeys)?.some(
                (item) => item === record[_rowKey]
              );
              selectionChange(!bool, record[_rowKey]);
            }

            e.stopPropagation();
          };

          if (!onDoubleClick) return onClick();

          if (currRowClick.current === record[_rowKey]) {
            // 双击
            onDoubleClick?.(record);
            clearCurrRowClick();
          } else {
            currRowClick.current = record[_rowKey];

            rowClickTimer.current = window.setTimeout(() => {
              onClick();
              clearCurrRowClick();
            }, 300);
          }
        }
      },
    };
  };

  const components = useMemo(() => {
    // 托动改变宽度
    if (!extra?.some((item) => item.headerRender))
      return {
        header: {
          cell: ResizeableTitle
        }
      };

    return {
      header: {
        cell(props: any) {
          const dataIndex = props.className.split(" ")[1];
          const curExtra = extra.find((item) => item.dataIndex === dataIndex);

          if (!curExtra?.headerRender && !curExtra?.required) {
            return <ResizeableTitle {...props} />;
          }

          return (
            <th className={styles.headth} {...props}>
              <div className={styles.thExtra}>
                {curExtra.required && <span className={styles.required}>*</span>}
                <span {...props} className={styles.marginRight} />
                {curExtra.headerRender?.()}
              </div>
            </th>
          );
        },
      },
    };
  }, [extra, columns]);

  const pagination = {
    ...paginationConfig,
    pageSize,
    pageSizeOptions,
    total: count,
    current: page,
    onChange: paginationChange,
  };

  const rowSelection = {
    renderCell,
    type: rowSelectionType,
    fixed: true,
    selectedRowKeys: selectedRowKeys[page] || emptyArray,
    onSelectAll: onSelectAll,
  };

  const _scroll = { ...scroll, ...propScroll };
  const _rowSelection = rowSelectionType ? rowSelection : undefined;
  const wrapClass = classnames(styles.box, {
    [styles.noFlexBox]: flat || limitMaxHeight,
  });
  const locale = {
    emptyText: <Empty autoLoad={autoLoad} firstLoading={firstLoading} />,
  };


  const onTableChange = useCallback(
    (pagination, filters, sorter, extra) => {
      console.log(sorter, recordSort);
      if (recordSort) {
        setSorterKey({ [recordSort!]: { [sorter.field!]: sorter } });
        _setSorterKey(sorter, recordSort);
      }
    },
    [recordSort]
  );

  return (
    <div className={wrapClass}>
      <Skeleton loading={!_columns.length}>
        <div ref={boxRef} className={styles.table}>
          <AntdTable
            rowKey={_rowKey}
            rowSelection={_rowSelection}
            components={components}
            scroll={_scroll}
            bordered={false}
            size="small"
            loading={loading}
            columns={_columns as any}
            dataSource={_dataSource}
            onRow={onRow}
            onChange={onTableChange}
            pagination={false}
            locale={locale}
          />
        </div>

        {isPagination && <Pagination {...pagination} />}

        {toolNode && flatSelectedRowKeys(selectedRowKeys).length > 0 ? (
          <div className={styles.toolBarSeize} />
        ) : null}
      </Skeleton>

      {toolNode && (
        <ToolBar
          loading={loading}
          total={flatSelectedRowKeys(selectedRowKeys).length}
          checked={currentPageChecked}
          toolNode={toolNode}
          onChange={onSelectAll}
        />
      )}
    </div>
  );
}

export default memo(forwardRef(Table));
